#include "thread/cabstractthread.h"
#include "ipc/cautomutexlocker.h"

using namespace PILO::Core::Thread;

namespace PILO 
{
    namespace Core
    {
        namespace Thread 
        {
#ifdef      WINDOWS
            const int Win32ThreadPriorities[] = {
                THREAD_PRIORITY_IDLE,
                THREAD_PRIORITY_LOWEST,
                THREAD_PRIORITY_BELOW_NORMAL,
                THREAD_PRIORITY_NORMAL,
                THREAD_PRIORITY_ABOVE_NORMAL,
                THREAD_PRIORITY_HIGHEST,
                THREAD_PRIORITY_TIME_CRITICAL
            };

            ThreadPriorityEnumeration __local_thread_priority_to_os_independent_priority(int priority)
            {
                for (int i=0; i<(sizeof(Win32ThreadPriorities)/sizeof(int)); i++) {
                    if (Win32ThreadPriorities[i] == priority) {
                        return (ThreadPriorityEnumeration) i;
                    }
                }
                return EnumPriorityInvalid;
            }

            THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL ThreadStartRoutineCallback(void* param)
            {
                CAbstractThread* thisThread = (CAbstractThread*)param;

                thisThread->_m_state = EnumStateRunning;
                thisThread->_m_resultCode = thisThread->onRun();
                thisThread->_m_state = EnumStateFinished;
                thisThread->onFinished(); // do Some User task

                _endthreadex(0);
                return 0;
            }

#else       
            INT32 __os_independent_priority_to_linux_priority(int policy, ThreadPriorityEnumeration priority)
            {
                int max = sched_get_priority_max(policy);
                int min = sched_get_priority_min(policy);                

                int delta = (max - min);
                if (delta < 0) {
                    return min; //should not be here
                }

                int step = (delta / (EnumPriorityInvalid - 1));

                if (EnumPriorityCritical == priority) {
                    return max;
                } else if (EnumPriorityIdel == priority) {
                    return min;
                }
                int retPriority = min + (priority)*step; 

                return retPriority;        
            }
            ThreadPriorityEnumeration __local_thread_priority_to_os_independent_priority(int priority)
            {
                int currentIndex;
                int comparand = INT_MAX;
                int max = sched_get_priority_max(SCHED_RR);
                int min = sched_get_priority_min(SCHED_RR);

                if ((min < priority) && (priority < max)) {
                    for (int i=0; i<EnumPriorityInvalid; i++) {
                        int refPriority = __os_independent_priority_to_linux_priority(SCHED_RR, (ThreadPriorityEnumeration) i);
                        int delta = priority - refPriority;

                        if (delta == 0) {
                            return (ThreadPriorityEnumeration) i;
                        } else if (delta <0) {
                            delta = 0-delta;
                        }

                        if (delta < comparand) {
                            currentIndex = i;
                            comparand = delta;
                        }
                    }
                    return (ThreadPriorityEnumeration) currentIndex;

                }               
                return EnumPriorityInvalid;
            }
            static void thread_sleep(struct timespec *ti)
            {
                pthread_mutex_t mtx;
                pthread_cond_t cnd;

                pthread_mutex_init(&mtx, 0);
                pthread_cond_init(&cnd, 0);

                pthread_mutex_lock(&mtx);
                (void) pthread_cond_timedwait(&cnd, &mtx, ti);
                pthread_mutex_unlock(&mtx);

                pthread_cond_destroy(&cnd);
                pthread_mutex_destroy(&mtx);
            }

            THREAD_ROUTINE_RETURN_TYPE THREAD_ROUTINE_CALL ThreadStartRoutineCallback(void* param)
            {
                pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
                CAbstractThread* thisThread = (CAbstractThread*)param;
                struct CThreadAutoFinalizer
                {                                                                                                       
                    CAbstractThread*    m_thread;
                    CThreadAutoFinalizer(CAbstractThread *thr) { m_thread = thr; }
                    ~CThreadAutoFinalizer()
                    {
                        if ((EnumStatePreparing == m_thread->_m_state) || (EnumStateRunning == m_thread->_m_state)) {
                            m_thread->_m_state = EnumStateAborted;
                            try { 
                                m_thread->onThreadPostKill(); 
                            } catch (...) {

                            }
                        } else if (EnumStateFinished == m_thread->_m_state) {
                            ;
                        } else {
                            throw EC_THREAD_STATE_ERROR;
                        }

                    }
                } AutoFinalizer(thisThread);
                

                thisThread->_m_state = EnumStateRunning;
                pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
                pthread_testcancel();
                thisThread->_m_resultCode = thisThread->onRun();
                thisThread->_m_state = EnumStateFinished;

                thisThread->onFinished(); // do Some User task

                pthread_exit((void*) 0);
                return 0;             
            }
#endif
            CAbstractThread::CAbstractThread()
            {
                _m_creationStackSize = 0;
                _m_threadID = 0; 
                _m_resultCode = 0;
                _m_creationDetachState = FALSE;
                _m_isDetached = FALSE;
                _m_bTerminationRequired = FALSE;
                _m_state = EnumStateNotStarted;
            }

            BOOL CAbstractThread::getResult( INT32& resultCode )
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if ((_m_state>> 31) & 1) { //Aborted
                    return EC_THREAD_ABORTED;
                } else {
                    if ((EnumStateFinished != _m_state) && (EnumStateFinishedAndJoined != _m_state)) {
                        return EC_THREAD_STATE_ERROR;
                    }
                }

                resultCode = _m_resultCode;
                return EC_OK;
            }

            INT32 CAbstractThread::setThreadCreationDetachedState( BOOL isEnable )
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if (_m_creationDetachState != isEnable) {
                    _m_creationDetachState = isEnable;
                    return EC_OK;
                }
                
                return EC_ALREADY_DONE;
            }

#           ifdef WINDOWS       

            INT32 CAbstractThread::startThread()
            {
                CAutoMutexLocker autoLocker(&_m_lock);
           
                //Can not run twice in the same time.
                if (! _isStopped()) {
                    return EC_THREAD_STATE_ERROR;
                }

                //reset detach state
                if (_m_isDetached) {
                    _m_isDetached = FALSE; 
                }

                //reset termination command
                _m_bTerminationRequired = FALSE;

                _m_state = EnumStatePreparing;

                //Create thread and start it, POSIX does not support create a "suspend" thread 
                _m_threadHandle =(HANDLE) _beginthreadex(
                    0, //Pointer to security descriptor
                    (unsigned int)_m_creationStackSize, //Stacksize
                    ThreadStartRoutineCallback, //Start routine
                    this, //Argument
                    0, //flag
                    &_m_threadID //ID of thread 
                    );

                if (_m_threadHandle == INVALID_HANDLE_VALUE) {
                    _m_state = EnumStateAborted;
                    _m_threadID = 0;                
                    if (_m_creationDetachState) {
                        _m_isDetached = FALSE;
                    }
                    return MAKE_SYSERR(EC_START_THREAD_FAILED);
                }

                // detach it if needed
                if (_m_creationDetachState) {
                    if (! ::CloseHandle(_m_threadHandle)) {
                        return MAKE_SYSERR(EC_DETACH_THREAD_FAILED);
                    }
                    _m_threadHandle = INVALID_HANDLE_VALUE;
                    _m_isDetached = TRUE;
                }

                return EC_OK;
            }

            INT32 CAbstractThread::detach()
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if ((EnumStatePreparing != _m_state) && (EnumStateRunning != _m_state)) {
                    return EC_THREAD_STATE_ERROR;
                }

                if (! _m_isDetached) {
                    //Do detach only if it is a running and joinable thread
                    if (! ::CloseHandle(_m_threadHandle)) {
                        return MAKE_SYSERR(EC_DETACH_THREAD_FAILED);
                    }
                    _m_threadHandle = INVALID_HANDLE_VALUE;
                    _m_isDetached = TRUE;
                    return EC_OK;
                }

                return EC_ALREADY_DONE;
            }

            INT32 CAbstractThread::waitThread()
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                DWORD tmpExitState;
                
                if (_m_threadID == ::GetCurrentThreadId()) {
                    return EC_INVALID_CALLER; //It can't not waits on itself!
                }
                
                if (_m_isDetached) {
                    if (INVALID_HANDLE_VALUE != _m_threadHandle) {
                       return EC_UNDEFINED; //Internal data error, i can't explain that for now. 
                    }
                    return EC_NONSENSE_OPERATION; //Wait on a detached thread in NO meaning.
                } else {
                    if (INVALID_HANDLE_VALUE == _m_threadHandle) {
                        return EC_UNDEFINED; //Internal data error, i can't explain that for now.
                    }
                }

                if ((EnumStatePreparing != _m_state) 
                    && (EnumStateRunning != _m_state) 
                    && (EnumStateFinished != _m_state)
                    && (EnumStateAborted != _m_state)) {
                        return EC_THREAD_STATE_ERROR;
                }

                INT32   rc;                
                switch (WaitForSingleObject(_m_threadHandle, INFINITE)) { //Only a running and joinable thread could be waited
                case WAIT_OBJECT_0:
                    GetExitCodeThread(_m_threadHandle, (LPDWORD)&tmpExitState);
                    if (0xFFFFFFFF==tmpExitState) {
                        _m_state = EnumStateAbortedAndJoined;
                        rc = EC_THREAD_ABORTED;
                    } else {
                        _m_state = EnumStateFinishedAndJoined;
                        rc = EC_OK;
                    }
                    ::CloseHandle(_m_threadHandle);
                    _m_threadHandle = INVALID_HANDLE_VALUE;
                    _m_threadID = 0;

                    break;
                case WAIT_FAILED:
                    rc = MAKE_SYSERR(EC_SYNC_THREAD_FAILED);
                    break;
                case WAIT_ABANDONED:
                    rc = EC_USER_CANCELED;
                    break;
                case WAIT_TIMEOUT:
                    rc = EC_TIMEOUT;
                    break;
                default:
                    rc = EC_UNDEFINED;
                    break;
                }
                return rc;               
            }

            //This could be very very dangerous! it will hang your process forever.
            INT32 CAbstractThread::killThread()
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if (EC_OK != _waitForStateTransitionFromPreparingToRunning()) {
                    return EC_THREAD_STATE_ERROR;
                }

                try { 
                    onThreadPreKill(); 
                } catch (...) {

                }

                if (_m_isDetached) {//Process Detached threads
                    if (INVALID_HANDLE_VALUE != _m_threadHandle) {
                        return EC_UNDEFINED;
                    }

                    HANDLE tmpHandle = ::OpenThread(THREAD_TERMINATE, 0 , _m_threadID);
                    if (NULL == tmpHandle) {
                        return EC_THREAD_NOT_EXIST;
                    }

                    if (TRUE != TerminateThread(tmpHandle, 0xFFFFFFFF)) {
                        return MAKE_SYSERR(EC_KILL_THREAD_FAILED);
                    }

                    ::CloseHandle(tmpHandle);

                } else {
                    if (TRUE != TerminateThread(_m_threadHandle, 0xFFFFFFFF)) {
                        return MAKE_SYSERR(EC_KILL_THREAD_FAILED);
                    }
                }

                _m_state = EnumStateAborted;

                try { 
                    onThreadPostKill(); 
                } catch (...) {

                }

                return EC_OK;

            }

            INT32 CAbstractThread::getPriority(ThreadPriorityEnumeration& value)
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if ((EnumStatePreparing != _m_state) && (EnumStateRunning != _m_state)) {
                    return EC_THREAD_STATE_ERROR;
                }

                int tmpPriority = ::GetThreadPriority(_m_threadHandle);
                if (THREAD_PRIORITY_ERROR_RETURN == tmpPriority) {
                    return MAKE_SYSERR(EC_GET_THREAD_INFO_FAILED);
                }
                value = __local_thread_priority_to_os_independent_priority(tmpPriority);
                return EC_OK;
            }

            INT32 CAbstractThread::setPriority(ThreadPriorityEnumeration pri)
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if ((EnumStatePreparing != _m_state) && (EnumStateRunning != _m_state)) {
                    return EC_THREAD_STATE_ERROR;
                }

                int tmpPriority = ::GetThreadPriority(_m_threadHandle);
                if (THREAD_PRIORITY_ERROR_RETURN == tmpPriority) {
                    return MAKE_SYSERR(EC_GET_THREAD_INFO_FAILED);
                }

                if (tmpPriority == Win32ThreadPriorities[pri]) {
                    return EC_ALREADY_DONE;
                }

                // This level means restore
                if (EnumPriorityInvalid == pri) {
                    pri = EnumPriorityNormal;
                }

                if (!SetThreadPriority(_m_threadHandle, Win32ThreadPriorities[pri])) {
                    return MAKE_SYSERR(EC_SET_THREAD_INFO_FAILED);
                }

                return EC_OK;
            }

            void CAbstractThread::sleep(unsigned long secs)
            {
                ::Sleep(secs * 1000);
            }

            void CAbstractThread::msleep(unsigned long msecs)
            {
                ::Sleep(msecs);
            }

            void CAbstractThread::usleep(unsigned long usecs)
            {
                ::Sleep((usecs / 1000) + 1);
            }


#           else

            BOOL CAbstractThread::startThread()
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                //Can not run twice in the same time.
                if (! _isStopped()) {
                    return EC_THREAD_STATE_ERROR;
                }

                //reset detach state
                if (_m_isDetached) {
                    _m_isDetached = FALSE; 
                }

                //reset termination command
                _m_bTerminationRequired = FALSE;
                _m_state = EnumStatePreparing;                

                pthread_attr_t attr;     
                pthread_attr_init(&attr);

                if (0 != pthread_attr_setstacksize(&attr, _m_creationStackSize)) {
                    pthread_attr_destroy(&attr);
                    return MAKE_SYSERR(EC_SET_THREAD_INFO_FAILED);
                }

                if (_m_creationDetachState) {
                    if (0 != pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED))
                    {
                        pthread_attr_destroy(&attr);
                        return MAKE_SYSERR(EC_SET_THREAD_INFO_FAILED);
                    }
                    _m_isDetached = TRUE;
                } else {
                    if (0 != pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE))
                    {
                        pthread_attr_destroy(&attr);
                        return MAKE_SYSERR(EC_SET_THREAD_INFO_FAILED);
                    }
                    _m_isDetached = FALSE;
                }

                int rc = pthread_create(
                    &_m_threadID, 
                    &attr, 
                    ThreadStartRoutineCallback, 
                    this
                    ); 
                pthread_attr_destroy(&attr);

                if (0 != rc) {
                    if (_m_creationDetachState)
                    {
                        _m_isDetached = FALSE;
                    }
                    _m_threadID = 0;
                    _m_state = EnumStateAborted;
                    return MAKE_SYSERR(EC_START_THREAD_FAILED);
                }

                return EC_OK;
            }

            BOOL CAbstractThread::detach()
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if ((EnumStatePreparing != _m_state) && (EnumStateRunning != _m_state)) {
                    return EC_THREAD_STATE_ERROR;
                }

                if (! _m_isDetached) {
                    if (! _m_isDetached) {
                        //Do detach only if it is a running and joinable thread
                        if (0 != pthread_detach(_m_threadID)) {
                            return MAKE_SYSERR(EC_DETACH_THREAD_FAILED);
                        }
                        _m_isDetached = TRUE;
                        return EC_OK;
                    }
                }

                return EC_ALREADY_DONE;;
            }

            INT32 CAbstractThread::waitThread()
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if (_m_threadID == pthread_self()) {
                    return EC_INVALID_CALLER;
                }

                if ((EnumStatePreparing != _m_state) 
                    && (EnumStateRunning != _m_state) 
                    && (EnumStateFinished != _m_state)
                    && (EnumStateAborted != _m_state)) {
                        return EC_THREAD_STATE_ERROR;
                }

                if (_m_isDetached) {
                    return EC_NONSENSE_OPERATION;
                }

                if (0 !=pthread_join(_m_threadID,0)) {
                    return MAKE_SYSERR(EC_SYNC_THREAD_FAILED);
                }

                if (EnumStateAborted == _m_state) {
                    _m_state = EnumStateAbortedAndJoined;
                } else if (EnumStateFinished == _m_state) {
                    _m_state = EnumStateFinishedAndJoined;
                } else {
                    return EC_THREAD_STATE_ERROR;
                }

                return EC_OK;
                
            }

            INT32 CAbstractThread::killThread()
            {
                CAutoMutexLocker autoLocker(&_m_lock);

                if (EC_OK != _waitForStateTransitionFromPreparingToRunning()) {
                    return EC_THREAD_STATE_ERROR;
                }

                try { 
                    onThreadPreKill(); 
                } catch (...) {

                }

                _m_threadID = 0;
                if (pthread_cancel(_m_threadID) != 0) {
                    return MAKE_SYSERR(EC_KILL_THREAD_FAILED);
                }

                _m_state = EnumStateAborted;

                try { 
                    onThreadPostKill(); 
                } catch (...) {

                }

                return EC_OK;
            }

            INT32 CAbstractThread::getPriority(ThreadPriorityEnumeration& value)
            {
                CAutoMutexLocker autoLocker(&_m_lock);
                if (EnumStateRunning != _m_state) {
                    return EC_THREAD_NOT_RUNNING;
                }
                int policy;
                struct sched_param params;
                if (0 != pthread_getschedparam(pthread_self(), &policy, &params)) {
                    return MAKE_SYSERR(EC_GET_THREAD_INFO_FAILED);;
                }

                value = __local_thread_priority_to_os_independent_priority(params.sched_priority);
                return EC_OK;
            }

            INT32 CAbstractThread::setPriority(ThreadPriorityEnumeration pri)
            {
                CAutoMutexLocker autoLocker(&_m_lock);
                if (EnumStateRunning == _m_state) {
                    int currentPolicy;
                    int newPlicy;
                    struct sched_param params;
                    if (0 != pthread_getschedparam(_m_threadID, &currentPolicy, &params)) {
                        return MAKE_SYSERR(EC_GET_THREAD_INFO_FAILED);
                    }

                    if (EnumPriorityInvalid == pri) {
                        if (currentPolicy == SCHED_OTHER) {
                            return EC_ALREADY_DONE;
                        } else {
                            
                            newPlicy = SCHED_OTHER;
                            params.sched_priority = 0;
                        }

                    } else {
                        int tmpRequiredLocalPriority = __os_independent_priority_to_linux_priority(SCHED_RR, pri);
                        if (tmpRequiredLocalPriority == params.sched_priority) {
                            return EC_ALREADY_DONE;
                        } else {
                            newPlicy = SCHED_RR;
                            params.sched_priority = tmpRequiredLocalPriority;
                        }                        
                    }

                    if (0!=pthread_setschedparam(_m_threadID, newPlicy, &params)) {
                        return MAKE_SYSERR(EC_SET_THREAD_INFO_FAILED);
                    }

                    return EC_OK;
                }

                return EC_THREAD_NOT_RUNNING;
            }

            

            void CAbstractThread::sleep(unsigned long secs)
            {
                struct timeval tv;
                gettimeofday(&tv, 0);
                struct timespec ti;
                ti.tv_sec = tv.tv_sec + secs;
                ti.tv_nsec = (tv.tv_usec * 1000);
                thread_sleep(&ti);
            }

            void CAbstractThread::msleep(unsigned long msecs)
            {
                struct timeval tv;
                gettimeofday(&tv, 0);
                struct timespec ti;

                ti.tv_nsec = (tv.tv_usec + (msecs % 1000) * 1000) * 1000;
                ti.tv_sec = tv.tv_sec + (msecs / 1000) + (ti.tv_nsec / 1000000000);
                ti.tv_nsec %= 1000000000;
                thread_sleep(&ti);
            }

            void CAbstractThread::usleep(unsigned long usecs)
            {
                struct timeval tv;
                gettimeofday(&tv, 0);
                struct timespec ti;

                ti.tv_nsec = (tv.tv_usec + (usecs % 1000000)) * 1000;
                ti.tv_sec = tv.tv_sec + (usecs / 1000000) + (ti.tv_nsec / 1000000000);
                ti.tv_nsec %= 1000000000;
                thread_sleep(&ti);
            }            

            

            

#       endif

        }

    }    
}
